<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html> <head>
<title>Macro Testbed</title>
<script src="../../src/util/sets.js"></script>
<script src="../../src/util/freevars.js"></script>
<script src="../../src/util/alpharename.js"></script>
<script src="../../src/util/render.js"></script>
<script src="../../src/quasis/hygienicmacro.js"></script>
<script src="../../site/esparser/bundle.js"></script>
</head>

<body>
<h1>Demos <a href="http://wiki.ecmascript.org/doku.php?id=strawman:quasis-alt">Alternative Quasi-Literal Semantics</a></h1>
<script>
function expand(form) {
  setTimeout(function () {
      updateButtons(form, false, 'Working\u2026');
      try {
        doExpand(eval('(0,' + form.elements.handler.value + ')'),
                 form.elements.source.value, 'Program', form.elements.output);
      } catch (e) {
        throw e;
      } finally {
        updateButtons(form, true, 'Expand');
      }
    }, 0);
}

function updateButtons(form, enabled, text) {
  var buttons = form.getElementsByTagName('button');
  for (var i = buttons.length; --i >= 0;) {
    var button = buttons[i];
    if (button.type !== 'submit') { continue; }
    button.disabled = !enabled;
    while (button.firstChild) { button.removeChild(button.firstChild); }
    button.appendChild(document.createTextNode(text));
  }
}

function doExpand(handler, code, production, output) {
  // HACK: find backquoted sections and replace with a marker,
  // instead of mucking around with ometa.
  var t = Date.now();
  var extracted = {};
  var chunks = [];
  while (code.length) {
    var m = code.match(/^(?:\w+(?:[^`\w]|$)|[^\w`]+)+/);
    if (m) {
      chunks.push(m[0]);
      code = code.substring(m[0].length);
      continue;
    }
    chunks.push('("##' + ++t + '")');
    var prefix = code.match(/^(\w*)`/);
    var name = prefix[1] || 'defaultQuasi';
    var quasi = extracted[t] = ['QuasiExpr', { name: name }];
    var stack = [];
    var start = 0;
    for (var i = prefix[0].length, start = i; i < code.length; ++i) {
      var ch = code.charAt(i);
      if (ch === '`') {
        if (!stack.length) {
          quasi.push(['LiteralExpr', {type:'string',value:code.substring(start, i)}]);
          ++i;
          break;
        } else if (stack[stack.length - 1] === '`') {
          --stack.length;
        } else {
          stack[stack.length] = '`';
        }
      } else if (ch === '$') {
        var next = code.charAt(i + 1);
        if (next === '\\') {
          ++i;
        } else if (stack.length === 0) {
          if (next === '{') {
            quasi.push(['LiteralExpr', {type:'string',value:code.substring(start, i)}]);
            ++i;
            start = i + 1;
            stack.push('{');
          } else if (/[a-zA-Z_$]/.test(next)) {
            quasi.push(['LiteralExpr', {type:'string',value:code.substring(start, i)}]);
            var tail = code.substring(i + 1);
            m = tail.match(/^\w+/);
            quasi.push(['LiteralExpr', {type:'string',value:'(' + m[0] + ')'}]);
            i += m[0].length;
            start = i + 1;
          }
        }
      } else if (ch === '{') {
        if (stack.length) {
          stack.push('{');
        }
      } else if (ch === '}') {
        if (stack[stack.length - 1] === '{') {
          if (--stack.length === 0) {
            quasi.push(['LiteralExpr', {type:'string',
                                        value:'(' + code.substring(start, i) + ')'}]);
            start = i + 1;
          }
        }
      }
    }
    code = code.substring(i);
  }
  var ast = ES5Parser.matchAll(chunks.join(''), production, [], function () {});
  function walkAst(node) {
    if (node[0] === 'LiteralExpr' && node[1].type === 'string') {
      var m = node[1].value.match(/^##(\d+)$/);
      if (m && extracted.hasOwnProperty(m[1])) {
        var quasi = extracted[m[1]];
        var parts = [[], []];
        for (var i = 2, n = quasi.length; i < n; ++i) {
          if ((i & 1) && quasi[i][1].value.indexOf('`') >= 0) {
            // Recurse to substitutions
            doExpand(handler, quasi[i][1].value, 'Expression', quasi[i][1]);
            quasi[i][1].value = '(' + quasi[i][1].value + ')';
          }
          parts[i & 1].push(quasi[i][1].value);
        }
       
        var out = ES5Parser.matchAll(
            hygienicMacro(quasi[1].name, handler, parts[0], parts[1]),
            'Program', [], function () {});
        if (out.length === 3 && out[0] === 'Program') { out = out[2]; }
        return out;
      }
    }
    for (var i = 2, n = node.length; i < n; ++i) {
      node[i] = walkAst(node[i]);
    }
    return node;
  }
  var expanded = walkAst(ast);
  try {
    output.value = renderEcmascript(expanded);
  } catch (e) { output.value = '' + e; throw e; }
}
</script>
<style>textarea { font-family: monospace }</style>
<form onsubmit=expand(this);return(false)>
<button type=sumit>Expand</button>
<table><tr valign=top><td>
<h2>Handler</h2>
<textarea cols=80 rows=15 name=handler>function foo(literals, readSubsts) {
  function str(s) { return ['LiteralExpr', {type: 'string', value: s}]; }
  var ctor = ['NewExpr', {},
               ['MemberExpr', {}, readSubsts[0], str('Interpolation')],
               literals[0]];
  for (var i = 1, n = literals.length; i < n; ++i) {
    ctor.push(readSubsts[i], literals[i]);
  }
  return ctor;
}</textarea>

<h2>Source</h2>
<textarea cols=80 rows=10 name=source>myNode.innerHTML = foo`<foo>${bar}baz`</textarea>
<td>
<h2>Result</h2>
<textarea cols=80 rows=10 name=output disabled
 style="color:black; background-color: #eee"></textarea>
<h2>Known Issues</h2>
<ul>
  <li>Depends on ES5Parser which is very slow on TraceMonkey.  Use Chrome/Webkit.</li>
  <li>Recurse to embedded quasis in reverse order.
  <li>Hacks around parser since parser does not support quasi syntax.
  <li>Does not work with more than one quasi type.
  <li>Ignores name of quasi function.
  <li>Does not allow attaching helpers to quasi functions.
  <li>Inlines regardless of whether quasi function is deterministic after first-use.
</ul>
</table>
<button type=sumit>Expand</button>
</form>


<hr>
<address></address>
<!-- hhmts start --> Last modified: Tue Mar 23 19:25:50 PDT 2010 <!-- hhmts end -->
</body> </html>
